using System;
using System.Collections.Generic;

namespace Fadd.Commands
{
    /// <summary>
    /// This is a bit more advanced command pattern. The regular command pattern
    /// ties the command to the the class that handles the command. This
    /// pattern removes that binding and makes the commands independent of
    /// the logic that executes it.
    /// <para>
    /// The pattern also allows multiple handlers for each command, and you
    /// can also add handlers for unhandled commands.
    /// </para>
    /// </summary>
    /// <example>
    /// <code>
    /// // Map a handler to a command:
    /// dispatcher.Add(typeof(MyCommand), OnMyCommand);
    /// 
    /// // invoke my command:
    /// if (!dispatcher.Invoke(new MyCommand(myUserId)))
    ///   Console.WriteLine("No one handled the command!");
    /// </code>
    /// </example>
    public class CommandManager : ICommandDispatcher
    {
        /// <summary>
        /// When asynchronous invokes should be removed (if <see cref="EndInvoke"/> have not been called.)
        /// </summary>
        public static TimeSpan AsyncTimeout = new TimeSpan(1, 0, 0);

        /// <summary>
        /// Attributes list used for commands that have not been mapped.
        /// </summary>
        private readonly TypeMapping _attributes = new TypeMapping();

        /// <summary>
        /// Classes/interfaces list used for command that have not been mapped.
        /// </summary>
        private readonly TypeMapping _classes = new TypeMapping();

        /// <summary>
        /// Each command is mapped to different handlers 
        /// when the command is invoked. This is done once,
        /// then this list is used to invoke the commands (= speed increase)
        /// </summary>
        private readonly Dictionary<Command, MappedCommand> _commandHandlers = new Dictionary<Command, MappedCommand>();

        private readonly bool _dispatchAllToParent;

        /// <summary>
        /// Classes/interfaces list used for command that have not been mapped.
        /// </summary>
        private readonly TypeMapping _interfaces = new TypeMapping();

        private readonly CommandManager _parent;

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandManager"/> class.
        /// </summary>
        public CommandManager()
        {
            _parent = null;
        }

        /// <summary>
        /// Create a new command manager
        /// </summary>
        /// <param name="parent">Parent manager, it will receive all commands that are
        /// invoked in this instance, or just all unhandled ones.</param>
        /// <param name="dispatchAll">true if all commands should be sent to the parent; otherwise just all unhandled will be sent.</param>
        public CommandManager(CommandManager parent, bool dispatchAll)
        {
            _parent = parent;
            _dispatchAllToParent = dispatchAll;
        }

        #region ICommandDispatcher Members

        /// <summary>
        /// Add a command handler.
        /// </summary>
        /// <param name="type">Must be a class, an attribute.</param>
        /// <param name="handler">handler handling the command</param>
        /// <exception cref="ArgumentException">If handler have been added to that type already.</exception>
        /// <example>
        /// Program.Commands.Add(typeof(MyCommand), OnMyCommand);
        /// </example>
        public void Add(Type type, CommandHandler handler)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (handler == null)
                throw new ArgumentNullException("handler");

            if (type.IsSubclassOf(typeof (Attribute)))
                lock (_attributes)
                    _attributes.Add(type, handler);
            else if (type.IsInterface)
                lock (_interfaces)
                    _interfaces.Add(type, handler);
            else
                lock (_classes)
                    _classes.Add(type, handler);

			lock(_commandHandlers)
				foreach (KeyValuePair<Command, MappedCommand> pair in _commandHandlers)
					pair.Value.Dirty = true;
        }

        /// <summary>
        /// Remove a command handler.
        /// </summary>
        /// <param name="type">type to remove</param>
        /// <param name="handler">delegated that was mapped to the type.</param>
        public void Remove(Type type, CommandHandler handler)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (handler == null)
                throw new ArgumentNullException("handler");

            // Remove type from handlers.
            lock (_commandHandlers)
            {
                Queue<Command> _commandsToRemove = new Queue<Command>();

                // remove type from all handlers, and add a queue with all empty commands.
                foreach (KeyValuePair<Command, MappedCommand> pair in _commandHandlers)
                {
                    if (!pair.Value.Handlers.Contains(handler)) 
                        continue;

                    lock (pair.Value)
                    {
                        pair.Value.Handlers.Remove(handler);
                        if (pair.Value.Handlers.Count == 0)
                            _commandsToRemove.Enqueue(pair.Key);
                    }
                }

                while (_commandsToRemove.Count > 0)
					_commandHandlers.Remove(_commandsToRemove.Dequeue());
            }

            if (type.IsSubclassOf(typeof (Attribute)))
                lock (_attributes)
                    _attributes.Remove(type, handler);
            else if (type.IsInterface)
                lock (_attributes)
                    _interfaces.Remove(type, handler);
            else
                lock (_attributes)
                    _classes.Remove(type, handler);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">command to invoke.</param>
        /// <param name="ignoreMe">Handled that should not be invoked.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(object source, Command command, CommandHandler ignoreMe)
        {
            Check.Require(command, "command");

            command.SetHandled(false);
            if (!_commandHandlers.ContainsKey(command) && !AddNewCommand(command))
                return TriggerUnhandled(command);

            // invoke all handlers.
            MappedCommand mappedCmd;
            lock (_commandHandlers)
                mappedCmd = _commandHandlers[command];

            CommandEventArgs args = new CommandEventArgs(command);
            lock (mappedCmd)
            {
                // dirty means that the handlers have been changed.
                if (mappedCmd.Dirty)
                    MapCommand(command, mappedCmd.Handlers);

                foreach (CommandHandler handler in mappedCmd.Handlers)
                {
                    foreach (CommandHandler del in handler.GetInvocationList())
                    {
                        if (del == ignoreMe)
                            continue;

                        if (del(source ?? this, args))
                            command.SetHandled(true);

                        if (!args.CancelPropagation)
                            continue;

                        if (PropagationCancelled != null)
                            if (!PropagationCancelled(this, new PropagationEventArgs(command, del)))
                                continue;

                        return command.IsHandled;
                    }
                }
            }

            if (!command.IsHandled)
                return TriggerUnhandled(command);

            if (_dispatchAllToParent && _parent != null && !args.CancelPropagation)
                _parent.Invoke(command);

            return command.IsHandled;
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="command">command to invoke.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(Command command)
        {
            return Invoke(command, null);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="command">command to invoke.</param>
        /// <param name="ignoreMe">Handled that should not be invoked.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(Command command, CommandHandler ignoreMe)
        {
            return Invoke(null, command, ignoreMe);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">command to invoke.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(object source, Command command)
        {
            return Invoke(source, command, null);
        }


        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">Command to invoke</param>
        /// <param name="ignoreMe">Handler that should not receive the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        public IAsyncResult BeginInvoke(object source, Command command, CommandHandler ignoreMe, AsyncCallback callback, object state)
        {
            if (command == null)
                throw new ArgumentNullException("command");

            command.SetHandled(false);
            if (!_commandHandlers.ContainsKey(command) && !AddNewCommand(command))
            {
                TriggerUnhandled(command);
                return null;
            }

            // invoke all handlers.
            MappedCommand mappedCmd;
            lock (_commandHandlers)
                mappedCmd = _commandHandlers[command];

            AsyncQueueItemResult result;
            CommandEventArgs args = new CommandEventArgs(command);
            lock (mappedCmd)
            {
                // dirty means that the handlers have been changed.
                if (mappedCmd.Dirty)
                    MapCommand(command, mappedCmd.Handlers);

                result = new AsyncQueueItemResult(command, args, state);
                AsyncQueueItem queueItem = new AsyncQueueItem(source ?? this, command, this, callback, result);

                foreach (CommandHandler handler in mappedCmd.Handlers)
                {
                    foreach (CommandHandler del in handler.GetInvocationList())
                        if (del != ignoreMe)
                            queueItem.Add(del);
                }

                queueItem.BeginInvoke();
            }

            // hmm. Too tired, can't think. will this work as we want?
            // since we do begininvoke on both parent and us.
            //
            // we need to handle async res in some way.
            //
            // maybe a dictionary mapping our asyncres to the parent asyncres, to be able
            // to wait on both in 
            if (_dispatchAllToParent && _parent != null && !args.CancelPropagation)
                _parent.BeginInvoke(command, callback, state);


            return result;
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        public IAsyncResult BeginInvoke(object source, Command command, AsyncCallback callback, object state)
        {
            return BeginInvoke(source, command, null, callback, state);
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="ignoreMe">Handler that should not receive the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        public IAsyncResult BeginInvoke(Command command, CommandHandler ignoreMe, AsyncCallback callback, object state)
        {
            return BeginInvoke(null, command, ignoreMe, callback, state);
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        public IAsyncResult BeginInvoke(Command command, AsyncCallback callback, object state)
        {
            return BeginInvoke(null, command, null, callback, state);
        }

        /// <summary>
        /// Invoke this method when the command is complete, or if you want to wait
        /// on the command.
        /// </summary>
        /// <param name="res"></param>
        /// <returns></returns>
        public Command EndInvoke(IAsyncResult res)
        {
            if (res == null)
                throw new ArgumentNullException("res");

            AsyncQueueItemResult ares = (AsyncQueueItemResult) res;
            return ares.AsyncWaitHandle.WaitOne(AsyncTimeout, false) ? ares.Command : null;
        }

        /// <summary>
        /// Tells us if we have a handler for the specified type.
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>True if a handler have been registered otherwise false.</returns>
        public bool Contains(Type type)
        {
            return _classes.Contains(type) || _attributes.Contains(type) || _interfaces.Contains(type);
        }

        #endregion

        private event CommandHandler _unhandledCommands;

        /// <summary>
        /// Event that can override the <see cref="CommandEventArgs.CancelPropagation"/> property.
        /// </summary>
        /// <seealso cref="PropagationHandler"/>
        public event PropagationHandler PropagationCancelled;

        /// <summary>
        /// Handler for unhandled commands.
        /// </summary>
        /// <remarks>returning true will make the command look like it was handled</remarks>
        public event CommandHandler Unhandled
        {
            add { _unhandledCommands += value; }
            remove { _unhandledCommands -= value; }
        }

        /// <summary>
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        internal bool TriggerUnhandled(Command command)
        {
            if (command == null)
                throw new ArgumentNullException("command");

            if (_unhandledCommands != null && _unhandledCommands(this, new CommandEventArgs(command)))
            {
                command.SetHandled(true);
                return true;
            }

            if (!_dispatchAllToParent && _parent != null && _parent.TriggerUnhandled(command))
            {
                command.SetHandled(true);
                return true;
            }

            return false;
        }

        /// <summary>
        /// A command handler have canceled a command, this event gives you a chance to
        /// ignore that cancellation.
        /// </summary>
        /// <param name="command">command that was canceled.</param>
        /// <param name="handler">handler that canceled the command.</param>
        /// <returns>true means that we can cancel; false that we should continue</returns>
        internal bool InvokePropagationCancelled(Command command, CommandHandler handler)
        {
            if (PropagationCancelled != null)
                if (!PropagationCancelled(this, new PropagationEventArgs(command, handler)))
                    return false;

            return true;
        }

        /// <summary>
        /// Map a command that have never been mapped.
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        private bool AddNewCommand(Command command)
        {
            // add it if it doesnt exist.
            List<CommandHandler> handlers = new List<CommandHandler>();
            MapCommand(command, handlers);
            if (handlers.Count == 0)
                return false;

            lock (_commandHandlers)
            {
                if (!_commandHandlers.ContainsKey(command))
                    _commandHandlers.Add(command, new MappedCommand(handlers));
            }

            return true;
        }


        /// <summary>
        /// Map a command to all handlers.
        /// this is done to speed up the invoke process.
        /// </summary>
        /// <param name="command"></param>
        /// <param name="handlers"></param>
        private void MapCommand(Command command, ICollection<CommandHandler> handlers)
        {
            handlers.Clear();

            Type commandType = command.GetType();

            // invoke classes first since they are the most specific ones.
            lock (_classes)
                foreach (KeyValuePair<Type, List<CommandHandler>> pair in _classes)
                {
                    if (pair.Key.IsAssignableFrom(commandType))
                        foreach (CommandHandler handler in pair.Value)
                            handlers.Add(handler);
                }

            // map attributes
            object[] attributes = commandType.GetCustomAttributes(true);
            lock (_attributes)
                foreach (KeyValuePair<Type, List<CommandHandler>> pair in _attributes)
                {
                    foreach (object commandAttribute in attributes)
                    {
                        if (pair.Key.IsAssignableFrom(commandAttribute.GetType()))
                            foreach (CommandHandler handler in pair.Value)
                                handlers.Add(handler);
                    }
                }

            // map interfaces
            lock (_interfaces)
                foreach (KeyValuePair<Type, List<CommandHandler>> pair in _interfaces)
                {
                    foreach (Type type in commandType.GetInterfaces())
                    {
                        if (!pair.Key.IsAssignableFrom(type)) 
                            continue;

                        foreach (CommandHandler handler in pair.Value)
                            handlers.Add(handler);
                    }
                }
        }
    }
}
